package com.meaningful.thinking.aqs_demo;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;

public class MyLock {

    AtomicInteger state = new AtomicInteger(0);

    // 这些属性都是共享变量，需要考虑线程安全问题，为了解决该问题，使用原子引用
    Thread owner = null;

    AtomicReference<Node> head = new AtomicReference<>(new Node());

    AtomicReference<Node> tail = new AtomicReference<>(head.get());

    void lock() {
        if (state.get() == 0) {
            // 无限制的自旋，十分消耗cpu资源
            if (state.compareAndSet(0, 1)) {
                // 如果当前线程成功竞争到锁
                System.out.printf("%s,直接拿到了锁\n", Thread.currentThread().getName());
                owner = Thread.currentThread();
                return;
            }
        } else {
            // 非0状态首先判断是不是自己本省持有锁
            if (owner == Thread.currentThread()) {
                int i = state.incrementAndGet();
                System.out.printf("%s,重入了, 次数为%d\n", Thread.currentThread().getName(), i);
                return;
            }
        }

        // 假设没有获取到锁，那就丢去阻塞线程容器，随后阻塞住当前线程
        Node current = new Node();
        current.thread = Thread.currentThread();
        // 不断尝试将当前阻塞线程 挂接到阻塞容器的尾节点
        while (true) {
            Node currentTail = tail.get();
            if (tail.compareAndSet(currentTail, current)) {
                current.pre = currentTail;
                currentTail.next = current;
                System.out.printf("%s，没有获取到锁，加入AQS队列\n", Thread.currentThread().getName());
                break;
            }
            System.out.printf("%s,自旋尝试将当前线程挂接到AQS队列\n", Thread.currentThread().getName());
        }

        // 可以考虑阻塞解决无限制的自旋
        // 既然考虑阻塞了,就要考虑谁来唤醒阻塞的线程
        // 那要怎么唤醒，如果获取阻塞的线程，就需要一个保存被阻塞线程的容器,且只能唤醒指定的线程，不能全部唤醒
        // 切记虚假唤醒
        while (true) {
            // 应该唤醒哪个线程
            // 首先自己唤醒自己一次
            if (current.pre == head.get() && state.compareAndSet(0, 1)) {
                owner = Thread.currentThread();
                head.set(current);
                current.pre.next = null;
                current.pre = null;
                System.out.printf("%s被唤醒后拿到了锁\n", Thread.currentThread().getName());
                return;
            }
            LockSupport.park();
        }
    }

    void unlock() {
        // 假设当前的线程并不持有锁，它仍然能够解锁，这显然是不合理的
        if (owner == null || Thread.currentThread() != owner) {
            throw new RuntimeException("当前线程没有持有当前对象，无法解锁\n");
        }

        // 此时必定是同一个线程操作，所以以下的代码可以不用同步
        int i = state.get();
        if (i > 1) {
            System.out.printf("%s,解锁重入锁, 原本的次数%d\n", Thread.currentThread().getName(), i);
            state.set(i - 1);
            return;
        }
        if (i <= 0) {
            throw new RuntimeException(String.format("%s, 可重入锁解锁异常\n", Thread.currentThread().getName()));
        }

        // 一定持有锁了
        // 看看阻塞线程容器里面有没有自己
        // 如果有的话：证明我们已经执行完业务代码
        // 如果没有的话：证明我们还没有执行业务代码
        Node headNode = head.get();
        Node needWakeUp = headNode.next;
        state.set(0);
        if (needWakeUp != null) {
            System.out.printf("%s 唤醒了 %s\n", Thread.currentThread().getName(), needWakeUp.thread.getName());
            LockSupport.unpark(needWakeUp.thread);
        }
    }

    class Node {
        Node pre;
        Node next;
        Thread thread;
    }

}
